﻿using System;
using System.Linq;
using Gumini.JulaDB.Enums;
using Gumini.JulaDB.Expressions.Functions;
using Gumini.JulaDB.Parser;
using Gumini.JulaDB.Provider;
using Irony.Parsing;

namespace Gumini.JulaDB.Expressions
{
    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    internal class ExpressionBuilder
    {
        private readonly SqlGrammar _grammar;

        public ExpressionBuilder(SqlGrammar grammar)
        {
            _grammar = grammar;
        }

        private static string ParseOperator(ParseTreeNode node)
        {
            return
                node.ChildNodes.Any() ?
                node.ChildNodes[0].Term.Name :
                node.Term.Name;
        }

        /// <summary>
        /// A recursive method for building an expression tree based on the parse tree.
        /// </summary>
        public Expression CreateExpression(ParseTreeNode expressionNode)
        {
            if (expressionNode == null)
                throw new ArgumentNullException("expressionNode");

            Expression result;
            if (expressionNode.Term == _grammar.binExpr)
            {
                var leftNode = expressionNode.ChildNodes[0];
                var operatorNode = expressionNode.ChildNodes[1];
                var rightNode = expressionNode.ChildNodes[2];

                var leftExpression = CreateExpression(leftNode);
                var rightExpression = CreateExpression(rightNode);
                string op = ParseOperator(operatorNode);
                result = new BinaryExpression(op, leftExpression, rightExpression);
            }
            else if (expressionNode.Term == _grammar.unExpr)
            {
                var operatorNode = expressionNode.ChildNodes[0];
                var childNode = expressionNode.ChildNodes[1];

                var childExpression = CreateExpression(childNode);
                string op = ParseOperator(operatorNode);
                result = new UnaryExpression(op, childExpression);
            }
            else if (expressionNode.Term == _grammar.funCall)
            {
                var idNode = expressionNode.ChildNodes[0];
                var argsNode = expressionNode.ChildNodes[1];
                string functionName = idNode.ChildNodes[0].Token.ValueString;

                var selectNode = argsNode.GetSubNode(_grammar.selectStmt);
                if (selectNode != null)
                    throw new JulaDbException(ErrorCode.UnsupportedSyntax, "SELECT statement is not supported in expressions.");

                var argumentsNode = argsNode.GetSubNode(_grammar.exprList);
                int numberOfArguments = argumentsNode.ChildNodes.Count;

                var function = FunctionFactory.GetFunction(functionName, numberOfArguments);
                var childExpressions = argumentsNode.ChildNodes.Select(n => CreateExpression(n)).ToList();
                if (function is ScalarFunction)
                    result = new ScalarFunctionCall((ScalarFunction)function, childExpressions);
                else
                    result = new AggregateFunctionCall((AggregateFunction)function, childExpressions.Single());
            }
            else if (expressionNode.Term == _grammar.Id)
            {
                result = new Variable(expressionNode.GetName(_grammar, true));
            }
            else if (expressionNode.Term == _grammar.string_literal || expressionNode.Term == _grammar.number)
            {
                result = new Constant(expressionNode.Token.Value);
            }
            else if (expressionNode.Term == _grammar.TRUE)
            {
                result = new Constant(true);
            }
            else if (expressionNode.Term == _grammar.FALSE)
            {
                result = new Constant(false);
            }
            else if (expressionNode.Term == _grammar.exprList)
            {
                if (expressionNode.ChildNodes.Count > 1)
                    throw new JulaDbException(ErrorCode.SyntaxError, "Expression cannot contain list of expressions.");
                result = CreateExpression(expressionNode.ChildNodes[0]);
            }
            else if (expressionNode.Term == _grammar.NULL)
            {
                result = new Null();
            }
            else if (expressionNode.Term == _grammar.asterisk)
            {
                result = new Asterisk();
            }
            else if (expressionNode.Term == _grammar.isNullExpr)
            {
                var childExpr = CreateExpression(expressionNode.ChildNodes.First());
                if (expressionNode.ChildNodes.Count != 4)
                    throw new InvalidOperationException("Unexpected isNullExpr child count.");
                switch (expressionNode.ChildNodes[2].ChildNodes.Count)
                {
                    case 0: // expr IS NULL
                        result = new IsNullCondition(false, childExpr);
                        break;
                    case 1: // expr IS NOT NULL
                        result = new IsNullCondition(true, childExpr);
                        break;
                    default:
                        throw new InvalidOperationException("Unexpected notOpt child count.");
                }
            }
            else
                throw new InvalidOperationException(String.Format("Expected expression, got {0}.", expressionNode.Term.ToString()));

            return result;
        }
    }
}
